package AFMAnalysis;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.lang.Math;


public class Solver {

	int[] counts;
	int[] frequencies;
	int[] sizes;
	double[] rValues;
	double[] pairFunction;
	double[] theoreticalFreqs;
	double[] theoreticalRValues;
	int parallel;
	int perpendicular;
	int threshold;
	double averagePerpSize;
	double average;
	double criticalRValue;

	public void countPixels(ArrayList<Blob> blobArray){

		this.counts = new int[blobArray.size()];
		Blob currentBlob = null;
		Pixel current = null;
		int counter;
		for(int count = 0; count < blobArray.size(); count = count + 1){
			currentBlob = blobArray.get(count);
			current = currentBlob.head;
			counter = 0;
			while(current != null){
				counter++;
				current = current.next;
			}
			this.counts[count] = counter;
			currentBlob.size = counter;
		}
		Quicksort sorter = new Quicksort();
		sorter.sort(this.counts);
	}


	public void findFrequencies(){

		int[] tempSizes = new int[this.counts.length];
		int[] tempFrequencies = new int[this.counts.length];
		int index = 0;
		int counter = 0;
		int frequency = 0;
		boolean searching;
		while(index < this.counts.length){
			searching = true;
			tempSizes[counter] = this.counts[index];
			index++;
			frequency = 1;
			while(index < this.counts.length && searching == true){
				if(this.counts[index] == this.counts[index-1]){
					index++;
					frequency++;
				}
				else{
					searching = false;
				}
			}
			tempFrequencies[counter] = frequency;
			counter++;
		}
		this.frequencies = new int[counter];
		this.sizes = new int[counter];
		for(int count = 0; count < counter; count = count + 1){
			this.frequencies[count] = tempFrequencies[count];
			this.sizes[count] = tempSizes[count];
		}
	}

	public void solve(ArrayList<Blob> blobArray, File location, String fileName, boolean create){

		this.countPixels(blobArray);
		this.findFrequencies();
		this.labelBlobs(blobArray);
		if(create){
			File file = new File(location.getAbsolutePath(), fileName + ".txt");
			if(!file.exists()){
				try {
					file.createNewFile();
				} catch (IOException e) {
					System.out.println("Cannot create file.");
				}
			}
			try {
				BufferedWriter output = new BufferedWriter(new FileWriter(file));
				for(int index = 0; index < this.sizes.length; index = index + 1){
					output.write(index + ", " + this.sizes[index] + ", " + this.frequencies[index]);
					output.newLine();
				}
				output.close();
			} catch (IOException e) {
				System.out.println("Can't find the file.");
			}
		}
	}

	public int determineThreshold(){

		int threshold = 0;
		int temp;
		int firstFreq = 0;
		int firstIndex = 0;
		int secondFreq = 0;
		int secondIndex = 0;
		int thirdFreq = 0;
		int thirdIndex = 0;
		int fourthFreq = 0;
		int fourthIndex = 0;
		int fifthFreq = 0;
		int fifthIndex = 0;
		int[] closest = new int[5];

		for (int count = 0; count < this.frequencies.length; count++){

			temp = this.frequencies[count];
			if (temp > firstFreq){
				fifthIndex = fourthIndex;
				fifthFreq = fourthFreq;
				fourthIndex = thirdIndex;
				fourthFreq = thirdFreq;
				thirdIndex = secondIndex;
				thirdFreq = secondFreq;
				secondIndex = firstIndex;
				secondFreq = firstFreq;
				firstIndex = count;
				firstFreq = temp;
			}
			else if(temp > secondFreq){
				fifthIndex = fourthIndex;
				fifthFreq = fourthFreq;
				fourthIndex = thirdIndex;
				fourthFreq = thirdFreq;
				thirdIndex = secondIndex;
				thirdFreq = secondFreq;
				secondIndex = count;
				secondFreq = temp;
			}
			else if(temp > thirdFreq){
				fifthIndex = fourthIndex;
				fifthFreq = fourthFreq;
				fourthIndex = thirdIndex;
				fourthFreq = thirdFreq;
				thirdIndex = count;
				thirdFreq = temp;
			}
			else if(temp > fourthFreq){
				fifthIndex = fourthIndex;
				fifthFreq = fourthFreq;
				fourthIndex = count;
				fourthFreq = temp;
			}
			else if(temp > fifthFreq){
				fifthIndex = count;
				fifthFreq = temp;
			}
		}

		closest[0] = firstIndex;
		closest[1] = secondIndex;
		closest[2] = thirdIndex;
		closest[3] = fourthIndex;
		closest[4] = fifthIndex;

		int counter = 0;
		ArrayList<Integer> consecs = new ArrayList<Integer>();
		consecs.add(this.sizes[firstIndex]);

		for(int count1 = 0; count1 < closest.length-1; count1++){
			for(int count = count1+1; count < closest.length; count++){

				if(Math.abs(this.sizes[closest[count1]]-this.sizes[closest[count]]) <= 5){
					counter++;
					if(!consecs.contains(this.sizes[closest[count1]])){
						consecs.add(this.sizes[closest[count1]]);
					}
					if(!consecs.contains(this.sizes[closest[count]])){
						consecs.add(this.sizes[closest[count]]);
					}
				}
			}
		}
		/*
		for(int count = 0; count < closest.length; count++){
			System.out.println(this.sizes[closest[count]]);
		}

		System.out.println(counter);*/

		if(counter >= 2 && this.frequencies[closest[0]] > 5 && this.frequencies[closest[1]] > 5){
			int sum = 0;
			for(int count = 0; count < consecs.size(); count++){
				sum = sum + consecs.get(count);
			}
			threshold = sum*2/consecs.size();
		}

		return threshold;
	}

	public void labelBlobs(ArrayList<Blob> blobArray){

		this.parallel = 0;
		this.perpendicular = 0;
		threshold = determineThreshold();
		//		System.out.println(threshold);
		for(int index = 0; index < blobArray.size(); index = index + 1){
			Pixel current = blobArray.get(index).head;
			if(blobArray.get(index).size <= threshold){
				while(current != null){
					if(current.isPerpendicular == false && current.isParallel == false){
						this.perpendicular++;
						current.isPerpendicular = true;
					}
					current = current.next;
				}
			}
			else{
				while(current != null){
					if(current.isPerpendicular == false && current.isParallel == false){
						this.parallel++;
						current.isParallel = true;
					}
					current = current.next;
				}
			}
		}
	}

	public double[][] determineCenters(ArrayList<Blob> blobArray){

		int counter;
		int sumX;
		int sumY;
		Pixel current;
		int numPerp = 0;
		for(int count = 0; count < blobArray.size(); count++){
			if(blobArray.get(count).head.isPerpendicular){
				numPerp++;
			}
		}
		double[][] averages = new double[numPerp][2];
		int index = 0;
		int masterCounter = 0;
		for(int count = 0; count < blobArray.size(); count++){
			current = blobArray.get(count).head;
			counter = 0;
			sumX = 0;
			sumY = 0;
			if(current.isPerpendicular){
				while(current.next != null){
					counter++;
					sumX+=current.x;
					sumY+=current.y;
					current = current.next;
				}
				masterCounter+=counter;
				averages[index][0] = (double)sumX/counter;
				averages[index][1] = (double)sumY/counter;
				index++;
			}
		}

		averagePerpSize = (double)masterCounter/numPerp;
		return averages;
	}

	public void actualRadialDistribution(ArrayList<Blob> blobArray, int size, int numPeaks){

		double[][] averages = determineCenters(blobArray);
		ArrayList<Double> distribution = new ArrayList<Double>();
		double distance, x1, y1, x2, y2;
		for(int count = 0; count < averages.length; count++){
			for(int count1 = count + 1; count1 < averages.length; count1++){
				x1 = averages[count][0];
				x2 = averages[count1][0];
				y1 = averages[count][1];
				y2 = averages[count1][1];
				distance = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
				distribution.add(distance);
			}
		}

		double sum = 0;
		double min = distribution.get(0);

		for(int count = 0; count < distribution.size(); count++){
			if(distribution.get(count) < min){
				min = distribution.get(count);
			}
			sum = sum + distribution.get(count);
		}
		average = sum/distribution.size();

		System.out.println("The average center-to-center distance is " + average);

		double increment = (average-min)/size;

		pairFunction = new double[size];
		int[] freqs = new int[size];
		rValues = new double[size];
		double current = min;

		for(int count = 1; count <= size; count++){
			for(int i = 0; i < distribution.size(); i++){
				if(distribution.get(i) <= (current+increment) && distribution.get(i) > current){
					freqs[count-1] = freqs[count-1] + 1;
				}
			}
			current = current + increment;
			rValues[count-1] = current;
		}

		pairFunction[0] = freqs[0]/(Math.PI*rValues[0]*rValues[0]);

		double criticalGValue = 0;
		boolean searching = true;

		for(int count = 1; count < rValues.length; count++){
			pairFunction[count] = freqs[count]/(Math.PI*(rValues[count]*rValues[count]-rValues[count-1]*rValues[count-1]));
			if((pairFunction[count] - pairFunction[count-1]) < 0 && searching){
				criticalRValue = rValues[count-1];
				criticalGValue = pairFunction[count-1];
				searching = false;
			}
		}

		double[] peakValues = getTheoreticalPeakValues(numPeaks);
		theoreticalRValues = new double[peakValues.length];
		theoreticalFreqs = new double[peakValues.length];

		theoreticalFreqs[0] = criticalGValue;

		for(int count = 0; count < peakValues.length; count++){
			theoreticalRValues[count] = Math.sqrt(peakValues[count])*criticalRValue;
		}

		for(int count = 1; count < theoreticalFreqs.length; count++){
			int currentIndex = 0;
			while(rValues[currentIndex] < theoreticalRValues[count]){
				currentIndex++;
			}
			theoreticalFreqs[count] = (pairFunction[currentIndex]+pairFunction[currentIndex-1])/2;
		}

	}

	public double[] getFreqs(){
		return pairFunction;
	}

	public double[] getTheoreticalFreqs(){
		return theoreticalFreqs;
	}

	public double[] getTheoreticalRValues(){
		return theoreticalRValues;
	}

	public double[] getRValues(){
		return rValues;
	}

	public double[] getTheoreticalPeakValues(int size){

		ArrayList<Integer> n = new ArrayList<Integer>();
		ArrayList<Integer> d = new ArrayList<Integer>();

		int m = 2;

		n.add(1);
		d.add(1);
		int count = 1;
		int dValue = 2;

		while(count < size){
			//Fill n values
			for(int i = 0; i < 2*m; i++){
				if((i+1)%2 != 0){
					n.add(i);
				}
			}
			for(int i = 0; i < 2*m; i++){
				if((i+1)%2 == 0){
					n.add(i);
				}
			}

			//Fill d values
			for(int i = 0; i < m; i++){
				d.add(dValue);
			}
			dValue++;
			for(int i = m; i < 2*m; i++){
				d.add(dValue);
			}
			dValue++;
			count = count + 2*m;
			m++;
		}

		double[] x = new double[n.size()];

		for(int index = 0; index < x.length; index++){
			double numerator = (double)n.get(index)/2;
			double denominator = (double)d.get(index)*(Math.sqrt(3)/2);
			x[index] = numerator/denominator;
		}

		double[] values = new double[x.length];

		for(int index = 0; index < values.length; index++){
			values[index] = 3*d.get(index)*d.get(index)*(1 + x[index]*x[index])/4;
			//System.out.print(values[index] + ", ");
		}

		return values;
	}

	public double[] movingAverage(int period){

		double[] movingAverage = new double[pairFunction.length];
		double sum;

		for(int count = 0; count < period-1; count++){
			movingAverage[count] = 0;
		}

		for(int count = period-1; count < movingAverage.length; count++){
			sum = 0;
			for(int index = 0; index < period; index++){
				sum = sum + pairFunction[count - index];
			}
			movingAverage[count] = sum/period;
		}

		return movingAverage;
	}

	public double[] idealRadialDistribution(int size){

		double[] values = getTheoreticalPeakValues(size);

		double averageDistance = 4*Math.sqrt(averagePerpSize/Math.PI);
		//System.out.println(averageDistance);

		double[] idealValues = new double[values.length];
		for(int index = 0; index < idealValues.length; index++){
			double distance = averageDistance*Math.sqrt(values[index]);
			idealValues[index] = distance/(Math.PI*distance*distance);
			//System.out.print(idealValues[index] + ", ");
		}

		return idealValues;
	}
}